package service

import (
	"archive/zip"
	"context"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/codeskyblue/go-sh"
	"github.com/gofiber/fiber/v2"
	logger "github.com/sirupsen/logrus"

	"publish-manage/internal/model"
	"publish-manage/pkg/config"
	"publish-manage/pkg/util"
)

var AgentPublishService = new(agentPublishService)

type agentPublishService struct{}

func (*agentPublishService) Publish(t int, file multipart.File, fileSize int64, publishPath, zipDir string, cmdAfter string) error {
	var err error

	if publishPath[len(publishPath)-1] == os.PathSeparator {
		publishPath = publishPath[:len(publishPath)-1]
	}

	// 备份publishPath目标文件
	err = backup(publishPath)
	if err != nil {
		return err
	}

	switch t {
	case 1: // 静态文件
		err = publishStatic(file, fileSize, publishPath, zipDir)
	case 2: // 直接部署
		err = publishProc(file, publishPath)
	case 3: // docker部署
		err = publishDocker(file, publishPath)
	default:
		err = errors.New("不支持的部署方式")
	}
	if err != nil {
		return err
	}
	if cmdAfter != "" {
		err = runCustomCmd(cmdAfter)
	}
	return err
}

func (s *agentPublishService) PublishPackage(agentPackage *model.AgentPackage) error {
	dst, err := ioutil.TempDir("", "pak_"+agentPackage.PackageId+"-")
	if err != nil {
		logger.Error(err)
		return err
	}
	publishPath := agentPackage.Path
	if publishPath[len(publishPath)-1] == os.PathSeparator {
		publishPath = publishPath[:len(publishPath)-1]
	}

	dir, filename := filepath.Split(publishPath)
	if agentPackage.Type == 1 {
		filename += ".zip"
	}

	filePath := filepath.Join(dst, filename)
	if err = s.download(agentPackage, err, filePath); err != nil {
		return err
	}
	// 下载完毕
	file, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer file.Close()
	if err = s.doPublish(file, dir, filename, agentPackage.ZipDir, agentPackage.Type, agentPackage.Command); err != nil {
		return err
	}

	return nil
}

func (s *agentPublishService) download(agentPackage *model.AgentPackage, err error, filePath string) error {
	downloadFile, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		return err
	}
	defer func() {
		_ = downloadFile.Close()
	}()

	request, err := http.NewRequest("GET", config.GetString("server.manageAddr")+"/agent/download", nil)
	if err != nil {
		return err
	}
	// 设置请求密钥信息
	request.Header.Set(fiber.HeaderAuthorization, util.HmacSha256(agentPackage.PackageId, agentPackage.AgentId))

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
	defer cancel()
	resp, err := http.DefaultClient.Do(request.WithContext(ctx))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode == 200 {
		if _, err = io.Copy(downloadFile, resp.Body); err != nil {
			return err
		}
		return nil
	} else {
		var bs []byte
		bs, err = io.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		return errors.New(fmt.Sprintf("管理端返回错误: [%d] - %s", resp.StatusCode, string(bs)))
	}
}

func (s *agentPublishService) doPublish(readFile *os.File, targetDir, targetFileName, zipDir string, t int, cmdAfter string) error {
	var err error
	publishPath := filepath.Join(targetDir, targetFileName)
	// 备份目标
	err = backup(publishPath)
	if err != nil {
		return err
	}

	//根据类型处理
	switch t {
	case 1: // 静态文件
		stat, _ := readFile.Stat()
		err = publishStatic(readFile, stat.Size(), publishPath, zipDir)
	case 2: // 直接部署
		err = publishProc(readFile, publishPath)
	case 3: //Docker 部署
		err = publishDocker(readFile, publishPath)
	default:
		err = errors.New("暂不支持的部署模式")
	}
	if err != nil {
		return err
	}
	if cmdAfter != "" {
		err = runCustomCmd(cmdAfter)
	}
	return err
}

func runCustomCmd(cmdAfter string) error {
	cmdList := strings.Split(cmdAfter, "\n")
	session := sh.NewSession()
	session.ShowCMD = true
	for i := 0; i < len(cmdList); i++ {
		splitCmd := strings.Split(cmdList[i], " ")
		session.Command(splitCmd[0], splitCmd[1:])
	}
	return session.Run()
}

func backup(p string) error {
	return sh.Command("cp", "-r", p, fmt.Sprintf("%s.bak%s", p, time.Now().Format("200601021504"))).Run()
}

func publishDocker(file multipart.File, path string) error {
	return nil
}

func publishProc(file multipart.File, path string) error {
	// 直接覆盖
	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.ModePerm)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(f, file)
	return err
}

func publishStatic(file multipart.File, size int64, p, zipDir string) error {
	// 获取父路径
	_, fname := filepath.Split(p)

	dst, err := ioutil.TempDir("", "pub_"+fname+"-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dst)

	archive, err := zip.NewReader(file, size)
	if err != nil {
		return err
	}
	for _, f := range archive.File {
		if err = unzipFile(f, dst); err != nil {
			logger.Error(err)
			return err
		}
	}

	// 复制到目标位置
	c := sh.Command("rm", "-rf", p).Command("mv", filepath.Join(dst, zipDir), p)
	//c := sh.Command(fmt.Sprintf("rm -rf %s && mv %s %s", p, filepath.Join(dst, "dist"), p))
	return c.Run()
}

func unzipFile(f *zip.File, dst string) error {
	filePath := filepath.Join(dst, f.Name)
	if !strings.HasPrefix(filePath, filepath.Clean(dst)+string(os.PathSeparator)) {
		return errors.New("文件路径不正确")
	}
	if f.FileInfo().IsDir() {
		logger.Debug("creating directory...", f.Name)
		if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
			return err
		}
		return nil
	}
	if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
		return err
	}
	fileInArchive, err := f.Open()
	if err != nil {
		return err
	}
	defer fileInArchive.Close()

	var dstFile *os.File
	dstFile, err = os.Create(filePath)
	if err != nil {
		return err
	}
	defer dstFile.Close()

	_, err = io.Copy(dstFile, fileInArchive)
	return err
}
